package com.esun.common.aspect;

import com.esun.common.enums.LogAgent;
import com.esun.common.enums.LogInfo;
import com.esun.common.log.DatabaseLogEo;
import com.esun.common.log.OperationLogEo;
import com.esun.common.mq.rabbitmq.producer.DatabaseLogProducer;
import com.esun.common.utils.SqlUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.session.SqlSessionFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @author John.xiao
 * @date 2021/10/12 10:21
 */
@Aspect
@Component
@Slf4j
@ConditionalOnExpression("${esun.log.database.enable:false}")
public class DatabaseLogAspect {
	@Pointcut("execution(public * com.esun.dao.mapper..*.*(..))")
	public void databaseLog() {
	}

	private final ThreadLocal<DatabaseLogEo> logThreadLocal = new ThreadLocal<>();

	private final Integer FINISH = 0;
	private final Integer UNTREATED = 1;
	private final Integer TREATED =2;
	@Resource
	DatabaseLogProducer databaseLogProducer;

	@Autowired
	private SqlSessionFactory sqlSessionFactory;

	@Around(value = "databaseLog()")
	public Object getAndSendLog(ProceedingJoinPoint joinPoint)throws Throwable {
		String sql = SqlUtils.getMybatisSql(joinPoint,sqlSessionFactory);

		LogInfo logInfo = LogAgent.get();
		DatabaseLogEo databaseLog = new DatabaseLogEo();
		BeanUtils.copyProperties(logInfo, databaseLog);
		databaseLog.setSqlStatement(sql);
		long startTime = System.currentTimeMillis();
		logThreadLocal.set(databaseLog);

		Object result = joinPoint.proceed();
		long endTime = System.currentTimeMillis();
		long totalTime = endTime - startTime;
		databaseLog.setCostTime(totalTime);
		databaseLog.setStatus(FINISH);

		try {
			databaseLogProducer.sendLog(databaseLog);
		} catch (Exception exception) {
			log.error("保存操作日志出错：", exception);
		} finally {
			logThreadLocal.remove();
		}
		return result;
	}

	@AfterThrowing(pointcut = "databaseLog()", throwing = "e")
	public void handle(JoinPoint point, Exception e) {
		String exception = stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace());
		DatabaseLogEo databaseLog = logThreadLocal.get();
		if (databaseLog != null) {
			int len = 2000;
			if (StringUtils.isNotEmpty(exception) && exception.length() > len) {
				exception = exception.substring(0, len);
			}
			databaseLog.setException(exception);
			databaseLog.setStatus(UNTREATED);
			try {
				databaseLogProducer.sendLog(databaseLog);
			} catch (Exception ex) {
				log.error("保存操作日志出错：", ex);
			} finally {
				logThreadLocal.remove();
			}
		}
	}

	/**
	 * 转换异常信息为字符串
	 *
	 * @param exceptionName    异常名称
	 * @param exceptionMessage 异常信息
	 * @param elements         堆栈信息
	 */
	public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
		StringBuilder strbuff = new StringBuilder();
		for (StackTraceElement stet : elements) {
			strbuff.append(stet).append("\n");
		}
		return exceptionName + ":" + exceptionMessage + "\n\t" + strbuff;
	}
}
